/* Copyright (c) 2003-2006 Adrian Pop [adrpo@ida.liu.se],
 * Programming Environments Laboratory
 * Linkoping University Sweden, All Rights Reserved
*******************************************************************************
  File        : "modelicaXML.cpp"
  Author      : Adrian Pop 2003-10-25
  Description : Antlr Parser caller and XML serializer
-------------------------------------------------------------------------------
  Source:
  Revision:
  Author:  Adrian Pop
  Date:    2004-05-12
  modification: 2004-11-01 - added directory walking, ModelicaXML class
  modification: 2006-03-12 - updated to Modelica 2.2 and better command line parameters
  modification  2006-11-29 - fix a Linux version
*******************************************************************************
*/


//**  HEADERS ************************************************************

// standard include files
#include <iostream>
#include <sstream>
#include <cstdlib>
#include <fstream>

// ModelicaParser related include file
// the following header files are automatically generated by the antlr parser (Do not modify them)
#include "modelica_lexer.hpp"
#include "modelica_parser.hpp"
#include "modelica_tree_parser.hpp"
#include "modelica_parserTokenTypes.hpp"
#include "modelica_tree_parserTokenTypes.hpp"
#include "parse_tree_dumper.hpp"

// antlr related include files
#include "antlr/AST.hpp"
#include "antlr/CommonAST.hpp"
#include "antlr/ASTFactory.hpp"
#include "MyAST.h"
#include "dirwalk.h"

using namespace std;

#define modelicaDTDUrl "http://www.ida.liu.se/~adrpo/modelica/xml/modelicaxml-v2.dtd"

class ModelicaXML
{
  typedef std::string mstring;
private:
  /* some xml helpers declarations */
  mstring *dtdUrl;
  DOMImplementation* pDOMImpl;
  DOMDocumentType* pDoctype;
  DOMDocument* pModelicaXMLDoc;
  DOMElement* pRootElementModelica;
  DOMLSSerializer* domSerializer;
  DOMLSOutput    * theOutputDesc;
  int serializeXMLDocumentToFile(mstring xmlFileName);
  DOMElement* createModelicaXMLDOMElement(const char *fileName);
  l_list &sortMoFiles(l_list &fileList);

public:
  // constructor
  ModelicaXML(char* dtdURL);
  // destructor
  ~ModelicaXML();
  void usageModelicaXML();
  int serializeMoFileToXML(char* fileName);
  int serializeEachMoFileInDirectory(char *directoryName);
  int serializeAllMoFilesInDirectoryInXMLFile(char *directoryName, char *fileName);
};

int usageModelicaXML(void)
{
  cout << "Usage:" << "ModelicaXML [-dtd dtdURL] [-f file1.mo file2.mo ... fileN.mo]"
       << "[-d directory] [-o file]" << endl;
  cout << "-dtd dtdURL\n\t sets the dtdURL as the dtd for the xml file:"
       << "c:\\path\\to\\modelica.dtd OR /home/path/to/modelica.dtd OR http://host.to/modelica.dtd"
       << "\n\t if none given, we use the default: " << modelicaDTDUrl << endl;
  cout << "-f file1.mo file2.mo ... fileN.mo\n\t transforms the file1-N.mo into xml version of "
       << "the files, named file1-N.mo.xml" << endl;
  cout << "-d directory\n\t transforms each file.mo files from the directory"
       << "\n\t AND subdirectories into xml version of files, named file.mo.xml" << endl;
  cout << "-d dir -o outputFile\n\t transforms ALL file.mo files from the directory"
       << "\n\t AND subdirectories one big xml file called outputFile.xml writen in dir" << endl;
  return 1;
}

//-----------------------------------------------------------------------------
// func         : main function for the modelica XML serializer
// author       : Adrian Pop 2003-10-25
// organization : Linkoping University Sweden, Programming Environment Laboratory
//-----------------------------------------------------------------------------
// the function accept as argument a flattened modelica file ad will generate as output the corresponding C files
// for the Modelica Simulator
int main( int argc, char* argv[] )
{
  // check if the modelica file is present in the argument list
    if (argc < 2)
    {
      return usageModelicaXML();
    }

    if (strcmp(argv[1],"--help") == 0)
    {
      return usageModelicaXML();
    }

    list<string*> argvList;
    cout << "Calling: " << argv[0] << " with arguments:\n\t ";
    for (int i = 1; i < argc; i++)
    {
      cout << argv[i] << " ";
      argvList.push_back(new string(argv[i]));
    }
    cout << endl << endl;

    list<string*> filesToConvert;
    string* directoryToConvert = NULL;
    string* serializedFile = NULL;
    string* dtdUrl = NULL;
    string* front = NULL;
    while (true)
    {
      if (!argvList.size()) break;
      front = argvList.front();
      argvList.pop_front();
      if (*front == "-f")
      {
  string* file = NULL;
  while (true)
  {
    if (!argvList.size()) break;
    file = argvList.front();
    if (!file)
    {
      if (filesToConvert.size() == 0) return usageModelicaXML();
      break;
    }
    if (*file == "-f" || *file == "--help" ||
        *file == "-d" || *file == "-dtd"   ||
        *file == "-o")
    {
      if (filesToConvert.size() == 0) return usageModelicaXML();
      break;
    }
    argvList.pop_front();
    filesToConvert.push_back(file);
  }
      }
      else
  if (*front == "-d")
  {
    if (!argvList.size()) break;
    string *directory = argvList.front();
    if (!directory || /* if is empty or if is command line parameter */
        (directory && (*directory == "-f" || *directory == "--help" ||
           *directory == "-d" || *directory == "-dtd"   ||
           *directory == "-o"))) return usageModelicaXML();
    /* else just collect the directory to convert */
    directoryToConvert = directory;
    argvList.pop_front();
  }
  else
    if (*front == "-dtd")
    {
      if (!argvList.size()) break;
      string *url = argvList.front();
      if (!url || /* if is empty or if is command line parameter */
    (url && (*url == "-f" || *url == "--help" ||
       *url == "-d" || *url == "-dtd"   ||
       *url == "-o"))) return usageModelicaXML();
      /* else just collect the dtd url */
      dtdUrl = url;
      argvList.pop_front();
    }
    else
      if (*front == "--help") return usageModelicaXML();
      else
        if (*front == "-o")
        {
    if (!argvList.size()) break;
    string *f = argvList.front();
    if (!f || /* if is empty or if is command line parameter */
        (f && (*f == "-f" || *f == "--help" ||
         *f == "-d" || *f == "-dtd"   ||
         *f == "-o"))) return usageModelicaXML();
    /* else just collect the output file */
    serializedFile = f;
    argvList.pop_front();
        }
        else return usageModelicaXML();
    }
    /* see what we have */
    ModelicaXML* pModelicaXML = NULL;
    if (dtdUrl) pModelicaXML = new ModelicaXML((char*)dtdUrl->c_str());
    else pModelicaXML = new ModelicaXML(NULL);

    bool somethingToDo = false;

    try
    {
      for (unsigned int i = 0; i < filesToConvert.size(); i++)
      {
        pModelicaXML->serializeMoFileToXML((char*)filesToConvert.front()->c_str());
        filesToConvert.pop_front();
        somethingToDo = true;
      }
      if (!serializedFile && directoryToConvert)
      {
        pModelicaXML->serializeEachMoFileInDirectory((char*)directoryToConvert->c_str());
        somethingToDo = true;
      }
      if (serializedFile && directoryToConvert)
      {
        pModelicaXML->serializeAllMoFilesInDirectoryInXMLFile(
          (char*)directoryToConvert->c_str(),
          (char*)serializedFile->c_str());
        somethingToDo = true;
      }
      if (!somethingToDo) usageModelicaXML();
    }
    catch(XMLException& e)
    {
      std::cerr << "XMLException: " << XMLString::transcode(e.getMessage()) << std::endl; exit (1);
    }
    catch(DOMException& e)
    {
      std::cerr << "DOMException: Code: " << e.code << " Msg: " << e.msg << std::endl; exit (1);
    }
    catch(std::exception& e)
    {
      std::cerr << "STDException: " << e.what() << std::endl; exit (1);
    }
    return 0;
}

ModelicaXML::ModelicaXML(char* dtdURL)
{
  // initialize xml framework
  XMLPlatformUtils::Initialize();

  // XML DOM creation
  pDOMImpl = DOMImplementationRegistry::getDOMImplementation(X("Core"));

  // create the document type (according to modelica.dtd)
  pDoctype =
    pDOMImpl->createDocumentType(
         X("modelica"),
         NULL,
         (dtdURL) ? X(string(dtdURL).c_str()) : X(string(modelicaDTDUrl).c_str()));

  // create the <program> root element
  pModelicaXMLDoc = pDOMImpl->createDocument(
               0,                    // root element namespace URI.
               X("modelica"),         // root element name
               pDoctype);                   // document type object (DTD).

  pRootElementModelica = pModelicaXMLDoc->getDocumentElement();

  // create the writer
  // get a serializer, an instance of DOMLSSerializer
  XMLCh tempStr[3] = {chLatin_L, chLatin_S, chNull};
  DOMImplementation *impl = DOMImplementationRegistry::getDOMImplementation(tempStr);
  // create the writer
  domSerializer = ((DOMImplementationLS*)impl)->createLSSerializer();
  theOutputDesc = ((DOMImplementationLS*)impl)->createLSOutput();
  static XMLCh*                   gOutputEncoding        = 0;
  // set user specified output encoding
  theOutputDesc->setEncoding(gOutputEncoding);

  DOMConfiguration* serializerConfig=domSerializer->getDomConfig();
  // set the pretty print feature
  if (serializerConfig->canSetParameter(XMLUni::fgDOMWRTFormatPrettyPrint, true))
    serializerConfig->setParameter(XMLUni::fgDOMWRTFormatPrettyPrint, true);
}

ModelicaXML::~ModelicaXML()
{
  // release the output decription
  theOutputDesc->release();
  // release the serializer
  domSerializer->release();
  // release the document
  pModelicaXMLDoc->release();
  // terminate the XML framework
  XMLPlatformUtils::Terminate();
}


int ModelicaXML::serializeEachMoFileInDirectory(char *directoryName)
{
  typedef std::string mstring;
  mstring filename;
  l_list dirList;
  getDirectoryStructure(directoryName, dirList);
  std::list<char*>::const_iterator it;
  for(it=dirList.begin(); it!=dirList.end(); ++it)
  {
    l_list fileList;
    getFileList(*it, fileList, "*.mo");
    if (fileList.size()) fileList = sortMoFiles(fileList);
    cout << "Found: " << fileList.size() << " *.mo files in directory: " << *it  << endl;
    if (!fileList.size()) { continue; }
    std::list<char*>::const_iterator itFileList;
    for(itFileList=fileList.begin(); itFileList!=fileList.end(); ++itFileList)
    {
      filename = mstring(*itFileList);
      cout << " [" << filename << "]" << endl;
      DOMElement* pModelicaXML = createModelicaXMLDOMElement(filename.c_str());
      // if is not NULL append it to the <modelica></modelica> element
      if (pModelicaXML) pRootElementModelica->appendChild(pModelicaXML);
      // vomit the current XML Document to file
      std::string xmlFile(filename.c_str());
      xmlFile += ".xml";
      serializeXMLDocumentToFile(xmlFile);
      if (pModelicaXML) pRootElementModelica->removeChild(pModelicaXML);
    }
    if (fileList.size()) cout << endl;
  }
  return 0;
}

int ModelicaXML::serializeAllMoFilesInDirectoryInXMLFile(char *directoryName, char *xmlFileName)
{
  int i=0;
  typedef std::string mstring;
  mstring filename;
  l_list dirList;
  getDirectoryStructure(directoryName, dirList);
  std::list<char*>::const_iterator it;
  for(it=dirList.begin(); it!=dirList.end(); ++it)
  {
    l_list fileList;
    getFileList(*it, fileList, "*.mo");
    // take care of pakage.mo first
    fileList = sortMoFiles(fileList);
    cout << "Found: " << fileList.size() << " *.mo files in directory: " << *it  << endl;
    std::list<char*>::const_iterator itFileList;
    for(itFileList=fileList.begin(); itFileList!=fileList.end(); ++itFileList)
    {
      filename = mstring(*itFileList);
      cout << " [" << *itFileList << "]" << endl;
      DOMElement* pModelicaXML = createModelicaXMLDOMElement(filename.c_str());
      // if is not NULL append it to the <modelica></modelica> element
      if (pModelicaXML)
      {
        pRootElementModelica->appendChild(pModelicaXML);
        i++;
      }
    }
    if (fileList.size()) cout << endl;
  }
  // vomit the current XML Document to file
  std::string xmlFile(xmlFileName);
  xmlFile = mstring(directoryName) + mstring(PATH_SEPARATOR) + xmlFile;
  xmlFile += ".xml";
  serializeXMLDocumentToFile(xmlFile);
  cout << "Found: " << i << " .mo files" << endl;
  cout << "Serialized to: " << xmlFile << endl;
  return 0;
}

DOMElement* ModelicaXML::createModelicaXMLDOMElement(const char* fileName)
{
  ifstream file;
  //ofstream wfile;

  //open the file passed as an argument
  file.open(fileName);

  DOMElement* pRootElementModelicaXML = NULL;

  //if the file cannot be opened
  if (!file)
  {
    cerr << "Could not open file: " << fileName << "\n";
    return NULL;
  }

  modelica_lexer *lexer = 0;
  modelica_parser *parser = 0;
  modelica_tree_parser *walker = 0;
  antlr::ASTFactory *my_factory = 0;

  try
  {
    my_factory = new antlr::ASTFactory("MyAST", MyAST::factory);
    lexer = new modelica_lexer(file);
    lexer->setFilename(fileName);
    parser = new modelica_parser(*lexer);
    parser->initializeASTFactory(*my_factory);
    parser->setASTFactory(&(*my_factory));
    parser->stored_definition();
    //wfile.open("output.txt");
    //wfile << parser.getAST()->toStringList() << std::endl;
    antlr::RefAST ast = parser->getAST();
    //parse_tree_dumper dumper(std::cout);
    //std::cout << std::flush;
    if (ast)
    {
      //dumper.dump(ast);
      walker = new modelica_tree_parser();
      walker->initializeASTFactory(*my_factory);
      walker->setASTFactory(&(*my_factory));
      pRootElementModelicaXML = walker->stored_definition(RefMyAST(ast), fileName, pModelicaXMLDoc);
    }
    else
    {
      //wfile << std::endl << "Parse error: <NULL> AST\n";
      std::cerr << std::endl << "Parse error: <NULL> AST\n";
      file.close();
      return NULL;
    }
  }
  catch(antlr::ANTLRException& e)
  {
    std::cerr << "Parser/Lexer/Walker Exception: " << e.toString() << std::endl;
    file.close();
    std::cerr << "ERROR! File:" << fileName << std::endl;
    return NULL;
  }
  catch(std::exception& e)
  {
    std::cerr << "STDException: " << e.what() << std::endl;
    file.close();
    std::cerr << "ERROR! File:" << fileName << std::endl;
    return NULL;
  }
  file.close();
  //std::cout << "SUCCESS! File:" << fileName << std::endl;
  //wfile << std::endl << "SUCCESS! File:" << fileName << std::endl;
  //wfile.close();
  // delete the lexer, parser and walker

  if (lexer) delete lexer;
  if (parser) delete parser;
  if (walker) delete walker;
  if (my_factory) delete my_factory;

  return pRootElementModelicaXML;
}

int ModelicaXML::serializeXMLDocumentToFile(mstring xmlFileName)
{
  // fix the file
  XMLFormatTarget *myFormatTarget = new LocalFileFormatTarget(X(xmlFileName.c_str()));
  theOutputDesc->setByteStream(myFormatTarget);

  // serialize a DOMNode to the local file "
  domSerializer->write(pModelicaXMLDoc, theOutputDesc);

  myFormatTarget->flush();

  delete myFormatTarget;

  return 0;
}

int ModelicaXML::serializeMoFileToXML(char* fileName)
{
  // ModelicaXML filename (normal operation)
  DOMElement *pModelicaXMLElement = createModelicaXMLDOMElement(fileName);
  if (pModelicaXMLElement) pRootElementModelica->appendChild(pModelicaXMLElement);
  // vomit the current XML Document to file
  std::string xmlFile(fileName);
  xmlFile += ".xml";
  serializeXMLDocumentToFile(xmlFile);

  XMLSize_t elementCount = pModelicaXMLDoc->getElementsByTagName(X("*"))->getLength();
  std::cout << std::endl;
  std::cout << "The tree serialized contains: " << elementCount << " elements." << std::endl;

  return 0;
}


l_list &ModelicaXML::sortMoFiles(l_list &fileList)
{
  bool bPackageExists = false;
  bool bExampleExists = false;
  mstring filename;
  mstring str_package;
  mstring str_example;
  std::list<char*>::iterator itFileList;
  for(itFileList=fileList.begin(); itFileList!=fileList.end(); ++itFileList)
  {
    filename = mstring(*itFileList);
    if (endsWith(filename, "package.mo"))
    {
      bPackageExists = true;
      str_package = filename;
      fileList.erase(itFileList);
      if (fileList.size() == 0) break;
      itFileList=fileList.begin();
    }
    if (endsWith(filename, "Example.mo"))
    {
      bExampleExists = true;
      fileList.erase(itFileList);
      str_example = filename;
      if (fileList.size() == 0) break;
      itFileList=fileList.begin();
    }
  }
  if (bPackageExists)
  {
    fileList.push_front(strdup((char*)str_package.c_str()));
  }
  if (bExampleExists)
  {
    fileList.push_back(strdup((char*)str_example.c_str()));
  }
  return fileList;
}
